package neuron.geometry;

import java.util.ArrayList;
import java.util.List;

import neuron.Coord;
import neuron.Dendrite;
import neuron.Neuron;
import neuron.Segment;
import neuron.Soma;

public class Transform {

	/**
	 * Translate neuron <n> so that cell body is in origo (0,0,0).
	 * @param n
	 */
	public static void translateToOrigo(Neuron n)
	{
		// get location of soma center
		Soma s = n.getSoma();
		if (s == null || s.center() == null) { 
			System.err.println("Can't translate soma to origo, soma not found!"); 
			return; 
		} 
		
		Vector3d vSoma = new Vector3d(s.center());
		vSoma.negate();
//		System.out.println("Translation vector " + vSoma);

		// translate coords
		for (Coord c : n.getAllCoordinates()) vSoma.translate(c);
	}

	/**
	 * @param n
	 */
	public static void translateNeuron(Neuron n, Vector3d ref)
	{
		// translate coords
		for (Coord c : n.getAllCoordinates()) ref.translate(c);
	}

	/**
	 * Rotate neuron <n> so that apical dendrite is facing upwards (Y-axis)
	 * @param n
	 */
	public static void rotateApicalToYAxis(Neuron n) 
	{
		Dendrite apical = n.getApicalDendrite();
		if (apical == null) return;
		Segment seg = apical.getRootSegment();
		Coord first = seg.firstCoordinate();
		
		List<Segment> apicalBase = apical.getApicalBasePath();
		
		Coord last = seg.lastCoordinate();
		double len = first.distanceTo(last);
		for (Segment s : apicalBase) {
			last = s.lastCoordinate();
			len = first.distanceTo(last);
//			System.out.println("Len " + len);
//			if (len > 100) break;
		}
		
		// Determine apical direction. Compute dir = lastCoord - firstCoord, from the first segment of apical dendrite.
		Vector3d dir = new Vector3d(last);
		dir.sub(new Vector3d(first));
		dir.normalize();  //  ||dir|| = 1.0
		System.out.println("Direction: " + dir);
		
		// Create rotation matrix, first in XY then in YZ. 
/*		double xAngle = Math.PI/2 - dir.angle(Vector3d.UX); // angle to y-axis
		double zAngle = Math.PI/2 - dir.angle(Vector3d.UZ); // angle to y-axis 
		Matrix33d mRot = Matrix33d.rotXY(xAngle).mulMat(Matrix33d.rotYZ(-zAngle));
*/
		// Create rotation matrix, first in XY then in YZ. 
		double thetaXY = Math.PI/2 - Math.atan2(dir.y, dir.x);
//		System.out.println("Theta xy " + thetaXY*180.0/3.1415);
		double thetaYZ = Math.PI/2 - Math.atan2(dir.y, dir.z);
	//	System.out.println("Theta yz " + thetaYZ*180.0/3.1415);
		
		double xyAngle = dir.angle(Vector3d.UY); // angle to y-axis
		double zAngle = Math.PI/2 - dir.angle(Vector3d.UZ); // angle to y-axis
		//System.out.println("xy angle " + xyAngle*180/3.141592);
		//System.out.println("test zAngle " + 180.0/3.141592*zAngle + ", xy " + 180.0/3.141592*xyAngle);

		Matrix33d mRot = Matrix33d.rotXY(thetaXY);//.mulMat(Matrix33d.rotYZ(-zAngle));

//		System.out.println("ROTATE");
		Vector3d test = mRot.mulVec(dir);
//		System.out.println(test);
		xyAngle = dir.angle(Vector3d.UY); // angle to y-axis
		zAngle = Math.PI/2 - test.angle(Vector3d.UZ); // angle to y-axis 
//		System.out.println("test zAngle " + 180.0/3.141592*zAngle + ", xy " + 180.0/3.141592*xyAngle);

		thetaXY = Math.PI/2 - Math.atan2(test.y, test.x);
//		System.out.println("TEST Theta xy " + thetaXY*180.0/3.1415);
		thetaYZ = Math.PI/2 - Math.atan2(test.y, test.z);
//		System.out.println("TEST Theta yz " + thetaYZ*180.0/3.1415);

		mRot = mRot.mulMat(Matrix33d.rotYZ(-thetaYZ));
		
//		System.out.println("ROTATE2");
		test = mRot.mulVec(dir);
//		System.out.println(test);
		xyAngle = dir.angle(Vector3d.UY); // angle to y-axis
		zAngle = Math.PI/2 - test.angle(Vector3d.UZ); // angle to y-axis 
//		System.out.println("test zAngle " + 180.0/3.141592*zAngle + ", xy " + 180.0/3.141592*xyAngle);

		thetaXY = Math.PI/2 - Math.atan2(test.y, test.x);
//		System.out.println("TEST2 Theta xy " + thetaXY*180.0/3.1415);
		thetaYZ = Math.PI/2 - Math.atan2(test.y, test.z);
//		System.out.println("TEST2 Theta yz " + thetaYZ*180.0/3.1415);


		// Rotate all coordinates 
		for (Coord c : n.getAllCoordinates()) {
			Vector3d v = new Vector3d(c);
			Vector3d w = mRot.mulVec(v);
			w.copyTo(c);
		}
		
		dir = new Vector3d(last);
		dir.sub(new Vector3d(first));
		dir.normalize();  //  ||dir|| = 1.0
//		System.out.println("Direction after rotation: " + dir);
		
	}
	
	/**
	 * Rotate neuron <n> so that basal endings give max variation in x-axis.
	 * @param n
	 */
	public static void rotateMaxVariation(Neuron n)
	{
		// get all ending coordinates
		CoordSet endCoords = new CoordSet();
		for (Dendrite d : n.basalDendrites()) {
			endCoords.addAll(d.getEndPoints());
		}

		// get variance direction
		Vector3d var = endCoords.var();
		var.normalize();
		
		// angle to x-axis
		double xAngle = var.angle(Vector3d.UX);

		// rotation matrix
		Matrix33d mRot = Matrix33d.rotXZ(-xAngle);
		
		// Rotate all coordinates 
		for (Coord c : n.getAllCoordinates()) {
			Vector3d v = new Vector3d(c);
			Vector3d w = mRot.mulVec(v);
			w.copyTo(c);
		}
	}

	/**
	 * @param n
	 */
	public static void rotateByVector(Neuron n, Vector3d ref)
	{		
		// angle to x-axis
		double xAngle = ref.angle(Vector3d.UX);

		// rotation matrix
		Matrix33d mRot = Matrix33d.rotXZ(-xAngle);
		
		// Rotate all coordinates 
		for (Coord c : n.getAllCoordinates()) {
			Vector3d v = new Vector3d(c);
			Vector3d w = mRot.mulVec(v);
			w.copyTo(c);
		}
	}

	public static void flipY(Neuron n)
	{
		for (Coord c : n.getAllCoordinates()) {
			c.y = -c.y;
		}
	}
}
